#include<bits/stdc++.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>

#define THREAD_COUNT (4)
#define ll long long
#define u32 unsigned int
using namespace std;
const int N = 4e6 + 5;
unordered_map<thread::id, int> mp_id;
int id_cnt = -1;

class ThreadPool {
public:
    ThreadPool(size_t);

    template<class F, class... Args>
    auto enqueue(F &&f, Args &&... args)
    -> std::future<typename std::result_of<F(Args...)>::type>;

    ~ThreadPool();

    void thread_mp();

private:
    // need to keep track of threads so we can join them
    std::vector<std::thread> workers;
    // the task queue
    std::queue<std::function<void()> > tasks;

    // synchronization
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

inline void ThreadPool::thread_mp() {
    for (std::thread &worker: workers)
        if (mp_id[worker.get_id()] == 0) {
            mp_id[worker.get_id()] = ++id_cnt;
        }
}

// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
        : stop(false) {
    for (size_t i = 0; i < threads; ++i)
        workers.emplace_back(
                [this, i] {
//                    mp_id[this_thread::get_id()] = i;
                    for (;;) {
                        std::function<void()> task;

                        {
                            std::unique_lock<std::mutex> lock(this->queue_mutex);
                            this->condition.wait(lock,
                                                 [this] { return this->stop || !this->tasks.empty(); });
                            if (this->stop && this->tasks.empty())
                                return;
                            task = std::move(this->tasks.front());
                            this->tasks.pop();
                        }

                        task();
                    }
                }
        );
}

// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F &&f, Args &&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
    using return_type = typename std::result_of<F(Args...)>::type;

    auto task = std::make_shared<std::packaged_task<return_type()> >(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
    );

    std::future<return_type> res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);

        // don't allow enqueueing after stopping the pool
        if (stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");

        tasks.emplace([task]() { (*task)(); });
    }
    condition.notify_one();
    return res;
}

// the destructor joins all threads
inline ThreadPool::~ThreadPool() {
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for (std::thread &worker: workers)
        worker.join();
}


struct Edge {
    u32 u, v;
    int c;

    bool friend operator<(Edge a, Edge b) {
        return a.v < b.v;
    }
} edge[N];

struct node {
    int v, c;
};

void prtime(string pos, timeval &time) {
    printf("%s=", pos.c_str());
    struct timeval now;
    gettimeofday(&now, NULL);
    printf("%dms\n", int((now.tv_sec * 1000 + now.tv_usec / 1000) - (time.tv_sec * 1000 + time.tv_usec / 1000)));
    time = now;
}

u32 nod[N];
unordered_map<u32, int> mp;
//unordered_map<u32, bool> has_node;
string toString[N];
vector<node> G1[N], G2[N];
int n, m;

string numtostrng(u32 a) {
    if (a == 0)return "0";
    string tmp = "";
    while (a) {
        tmp += (a % 10);
        a /= 10;
    }
    reverse(tmp.begin(), tmp.end());
    return tmp;
}

void build() {
    sort(nod + 1, nod + n + 1);
    n = unique(nod + 1, nod + n + 1) - nod - 1;
    for (int i = 1; i <= n; i++) {
        toString[i] = numtostrng(nod[i]) + ',';
        mp[nod[i]] = i;
    }
    sort(edge + 1, edge + m + 1);

    for (int i = 1; i <= m; i++) {
        int u = mp[edge[i].u], v = mp[edge[i].v], c = edge[i].c;
        G1[u].push_back({v, c});
        G2[v].push_back({u, c});
    }
}

//string

string path[N][5];

struct find_huan {
    int is[N], rt, xx[N];
    bool flag[N];
    node f[N], cur[10];
//    vector<node> G[N];
    int num;

    inline void add_ans(int now_len) {
        ++num;
        string &ss = path[rt][now_len - 3];
        for (int i = 1; i <= now_len; i++) {
            ss += toString[cur[i].v];
        }
        ss[ss.length() - 1] = '\n';
    }

    bool check(ll x, ll y) {
        y = y * 5;
        return y >= x && y <= 15 * x;
    }

    void dfs(int u, int step) {
        int stp = step + 1;

        for (auto te : G1[u]) {
            int v = te.v, c = te.c;
            if (v < rt || flag[v] || (stp > 4 && is[v] != rt)) continue;
            if (stp > 2 && !check(cur[step].c, c)) continue;
            cur[stp] = te;
            if (stp >= 3 && f[v].v == rt && check(c, f[v].c) && check(f[v].c, cur[2].c)) add_ans(stp);
            if (stp == 7) continue;
            flag[v] = true;
            dfs(v, stp);
            flag[v] = false;
        }
    }

    void dfs_ni(int u, int step) {

        int stp = step + 1;
        for (auto te : G2[u]) {
            int v = te.v, c = te.c;
            if (v < rt || flag[v]) continue;
            if (stp > 2 && !check(c, cur[step].c)) continue;
            cur[stp] = te;
            is[v] = rt;
            if (step == 3) continue;
            flag[v] = true;
            dfs_ni(v, stp);
            flag[v] = false;
        }
    }


    void start(int i) {
        rt = i;
        // 倒着一个点
        for (auto te : G2[i]) if (te.v > rt) f[te.v] = {i, te.c};
        cur[1] = {i, 0};
        flag[i] = true;
        dfs_ni(i, 1);
        dfs(i, 1);
        flag[i] = false;

    }
} T[THREAD_COUNT];


void find_thread(int i) {
    int j = mp_id[this_thread::get_id()];
//    printf("%d->%d\n", i, j);
    T[j].start(i);
}

void run_huan() {
    ThreadPool pool(THREAD_COUNT);
    pool.thread_mp();
    for (int i = 1; i <= n; i++)
        pool.enqueue(find_thread, i);
}

void solve(char *outputfile) {

    int num = 0;
    for (int i = 0; i < THREAD_COUNT; i++) num += T[i].num;
    FILE *fp = fopen(outputfile, "w");
    char buf[1024];
    int idx = sprintf(buf, "%d\n", num);
    buf[idx] = '\0';
    fwrite(buf, idx, sizeof(char), fp);
    for (int j = 0; j < 5; j++)
        for (int i = 1; i <= n; i++) {
            fwrite(path[i][j].c_str(), path[i][j].size(), 1, fp);
        }

    printf("%d\n", num);
}


int main() {
    timeval start;
    gettimeofday(&start, NULL);
//    char *inputfile = "/data/test_data.txt";
//    char *outputfile = "/projects/student/result.txt";
    char *inputfile = "./data/19630345/test_data.txt";
//    char *inputfile = "./data/43/test_data.txt";
//    char *inputfile = "./data/e_2/test_data.txt";
//    char *inputfile = "./data/e_1/test_data.txt";
    char *outputfile = "./z.out";
    /* mmap读文件开始 */
    char *data = NULL;
    int fd = open(inputfile, O_RDONLY);
    long size = lseek(fd, 0, SEEK_END);
    data = (char *) mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
    /* mmap结束 */

    u32 tp[3], tmp = 0;
    int ii = 0, j = 0;

    while (*data) {
        if (isdigit(*data)) tmp = tmp * 10 + (*data) - '0';
        else {
            tp[ii++] = tmp;
            if ((*data) != ',') {
//                ++data;
                ii = 0;
                edge[++m] = {tp[0], tp[1], (int) tp[2]};
            } else {
                nod[++n] = tmp;
            }
            tmp = 0;
        }
        ++data;
    }
    build();
    run_huan();
    solve(outputfile);


    prtime("总时间 ", start);

    return 0;
}